package com.gitee.l0km.aocache.plugin;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.maven.artifact.ArtifactUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.codehaus.mojo.aspectj.Module;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
import org.eclipse.aether.resolution.ArtifactResolutionException;
import org.eclipse.aether.resolution.ArtifactResult;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;

import com.google.common.collect.ImmutableSet;

public class RelocatingSupport{
	/** 切面库模块名称 */
	private static final String AOCACHE_CORE = "aocache-core";
	/** aocache 运行时依赖名称 */
	private static final String AOCACHE = "aocache";
	/** aocache 运行时依赖名称 */
	private static final String AOCACHEZ = "aocachez";
	/** aocache 组名 */
	private static final String AOCACHE_GROUP_ID = "com.gitee.l0km";
	/** 原始包名 */
	private static final String FROM_PKG = "org/aspectj";
	/** 重定位包名 */
	private static final String TO_PKG = "com/gitee/l0km/aocache/aspectj";
	
	/**
	 * 向原始AspectJ切面库数组中添加aocache-core模块（如果不存在）。
	 * 如果数组中已存在groupId为{@value #AOCACHE_GROUP_ID}的模块，则将其artifactId更新为{@value #AOCACHE_CORE}。
	 * 
	 * @param originalAspectLibaries 原始AspectJ库数组
	 * @return 添加或更新后的AspectJ库数组
	 */
	static Module[] addAocacheAjIfAbsent(Module[] originalAspectLibaries) {
		if (null == originalAspectLibaries) {
			originalAspectLibaries = new Module[0];
		}
		for (Module m : originalAspectLibaries) {
			if (AOCACHE_GROUP_ID.equals(m.getGroupId())) {
				if (!AOCACHE_CORE.equals(m.getArtifactId())) {
					m.setArtifactId(AOCACHE_CORE);
				}
				return originalAspectLibaries;
			}
		}

		Module aocacheAjModule = new Module();
		aocacheAjModule.setGroupId(AOCACHE_GROUP_ID);
		aocacheAjModule.setArtifactId(AOCACHE_CORE);

		Module[] modulesArray = Arrays.copyOfRange(originalAspectLibaries, 0, originalAspectLibaries.length + 1);
		modulesArray[modulesArray.length - 1] = aocacheAjModule;
		return modulesArray;
	}

	static void addAocacheAjArtifact(
			MavenProject project, 
			RepositorySystem repoSystem,
			RepositorySystemSession repoSession,
			List<RemoteRepository> remoteRepos,
			Log log
			) throws MojoExecutionException {

		// 1. 检查运行时依赖是否存在
		org.apache.maven.artifact.Artifact runtimeArtifact = project.getArtifacts().stream()
				.filter(d -> AOCACHE_GROUP_ID.equals(d.getGroupId()) 
						&& (AOCACHE.equals(d.getArtifactId())|| AOCACHEZ.equals(d.getArtifactId())))
				.findFirst()
				.orElseThrow(() -> new MojoExecutionException("Missing aocache/aocachez runtime artifact"));

		// 2. 构建aocache-core构件坐标
		String ajVersion = runtimeArtifact.getVersion();
		Artifact ajArtifact = new DefaultArtifact(
				AOCACHE_GROUP_ID, 
				AOCACHE_CORE, 
				"jar", 
				ajVersion
				);
		// 3. 检查是否已存在该构件
		boolean exists = project.getArtifacts().stream()
				.anyMatch(a -> 
				AOCACHE_GROUP_ID.equals(a.getGroupId()) &&
				AOCACHE_CORE.equals(a.getArtifactId()) &&
				ajVersion.equals(a.getVersion()) &&
				"jar".equals(a.getType()) &&
				(a.getClassifier() == null || a.getClassifier().isEmpty())
						);

		if (exists) {
			log.debug("aocache-core already exists in project artifacts");
			return;
		}

		// 4. 解析构件
		try {
			ArtifactResult result = repoSystem.resolveArtifact(
					repoSession,
					new ArtifactRequest(ajArtifact, remoteRepos, null)
					);

			// 5. 转换为Maven原生Artifact并注入
			org.apache.maven.artifact.Artifact mavenArtifact = 
					new org.apache.maven.artifact.DefaultArtifact( // 使用maven-core内置实现
							result.getArtifact().getGroupId(),
							result.getArtifact().getArtifactId(),
							result.getArtifact().getVersion(),
							"provided",
							result.getArtifact().getExtension(),
							result.getArtifact().getClassifier(),
							null
							);
			mavenArtifact.setFile(result.getArtifact().getFile());
			project.getArtifacts().add(mavenArtifact);
			log.debug("Added aocache-core artifact to project: " + ArtifactUtils.key(mavenArtifact));

		} catch (ArtifactResolutionException e) {
			throw new MojoExecutionException("Error resolving aocache-core artifact: " + ajArtifact, e);
		}
	}

	/**
	 * 重定位指定文件夹中的 AspectJ 包名引用。
	 *
	 * @param classesFolder 包含类文件的文件夹路径
	 * @param sourceRoots   源文件根路径集合
	 * @param includeSources 需要处理的源文件集合
	 * @param wrapEnable 是否执行 JoinPoint 包装指令注入
	 * @param log           日志记录器，用于输出处理信息
	 * @throws MojoExecutionException 如果在重定位过程中发生错误
	 */
	static void relocateAspectjReferences(String classesFolder, List<String> sourceRoots, Set<String> includeSources, boolean wrapEnable, Log log) throws MojoExecutionException {
		if (includeSources == null || includeSources.isEmpty()) {
			return; // 如果没有需要处理的源文件，直接返回
		}
		log.info("CLASS PROCESS MODE:" + (wrapEnable ? "WRAP" : "RELOCATE"));
		log.debug("Starting to relocate AspectJ package name references... " + classesFolder);
		Set<String> _sourceRoots = ImmutableSet.copyOf(sourceRoots);
		// 用于过滤的类文件前缀集合
		Set<String> includeClassPrefixes = includeSources.stream()
				.map(s->resolveClassFilePath(s, _sourceRoots, classesFolder))
				.collect(Collectors.toSet()); 
		Path outputPath = Paths.get(classesFolder);
		if (!Files.exists(outputPath) || !Files.isDirectory(outputPath)) {
			throw new MojoExecutionException("The specified folder path is invalid or inaccessible: " + classesFolder);
		}
		try (Stream<Path> stream = Files.walk(outputPath)) {
			stream.filter(p -> Files.isRegularFile(p) && isIncludeClassFile(p, includeClassPrefixes))
			.forEach(classFile -> processClass(classFile, log, wrapEnable));
		} catch (IOException e) {
			throw new MojoExecutionException("Package name relocation failed", e);
		} catch (RelocatingException e) {
			throw new MojoExecutionException("Failed to process class file: " + e.getMessage(), e);
		}
	}
	
	/**
	 * 解析给定源文件路径，返回对应的类文件路径(无后缀)。
	 *
	 * @param source        源文件路径，通常为类的全限定名。
	 * @param sourceRoots   源文件根目录集合，用于匹配源文件路径的前缀。
	 * @param classesFolder 类文件存放的文件夹路径。
	 * @return 返回标准化的类文件路径。
	 * @throws RelocatingException 如果在源文件根目录中未找到匹配的源文件路径。
	 */
	private static String resolveClassFilePath(String source, Set<String> sourceRoots, String classesFolder) {
		// 查找源文件路径中最后一个点的位置，用于后续去除文件后缀
	    int lastDotIndex = source.lastIndexOf('.');
	    // 如果源文件路径中包含点，则去除最后一个点之后的部分（即文件后缀）；否则保持原样
	    String noSuffix = lastDotIndex != -1 ? source.substring(0, lastDotIndex) : source;
	    // 遍历源文件根目录集合，尝试匹配和替换源文件路径的前缀
	    for (String sourceRoot : sourceRoots) {
	        // 如果去除后缀的源文件路径以任何一个源文件根目录开头，则构建并返回标准化路径
	        if (noSuffix.startsWith(sourceRoot)) {
	            return Paths.get(classesFolder, noSuffix.substring(sourceRoot.length() + 1)).toString();
	        }
	    }
	    // 如果遍历完所有源文件根目录后仍未找到匹配项，则抛出异常
		throw new RelocatingException("Cannot match the source file's source root directory in sourceRoots:" + source + ", sourceRoots:" + sourceRoots);
	}

	/**
	 * 判断给定的类文件路径是否包含在指定的类前缀集合中。
	 *
	 * @param path                 要检查的类文件路径
	 * @param includeClassPrefixes 包含的类前缀集合
	 * @return 如果路径对应的类文件名符合条件，则返回 true；否则返回 false。
	 */
	private static boolean isIncludeClassFile(Path path, Set<String> includeClassPrefixes) {
		String fileName = path.toString();
		if (fileName.endsWith(".class")) {
			if (includeClassPrefixes.contains(fileName.substring(0, fileName.lastIndexOf('.')))) {
				return true;
			}
			/** 如果是内部类，则尝试用内部类所在的顶级类名进行匹配 */
			return fileName.contains("$")
					&& includeClassPrefixes.contains(fileName.substring(0, fileName.indexOf('$')));
		}
		return false;
	}
	/**
	 * 处理指定的类文件，将其进行包名引用重定位处理。
	 *
	 * @param classFile 要处理的类文件路径
	 * @param log       日志记录器，用于记录处理信息
	 * @param wrapEnable 是否执行 JoinPoint 包装指令注入
	 * @throws RelocatingException 如果在处理过程中发生错误
	 */
	private static void processClass(Path classFile, Log log, boolean wrapEnable) {
		try (InputStream is = Files.newInputStream(classFile)) {
			log.debug("Processing class file: " + classFile);
			ClassReader cr = new ClassReader(is);
			ClassWriter cw = new ClassWriter(0);
			if(wrapEnable) {
				JoinPointWrapInjecter cv = new JoinPointWrapInjecter(cw, FROM_PKG, TO_PKG);
				cr.accept(cv, ClassReader.EXPAND_FRAMES);
				if (!cv.isIgnored()) {
					log.info("WRAP: " + classFile);
					Files.write(classFile, cw.toByteArray());
				}
			}else {
				RelocatingClassVisitor cv = new RelocatingClassVisitor(cw, FROM_PKG, TO_PKG);
				cr.accept(cv, 0);
				if (cv.modifiedCount.get() > 0) {
					log.info("RELOCATED: " + classFile);
					Files.write(classFile, cw.toByteArray());
				}

			}
		} catch (Exception e) {
			throw new RelocatingException(classFile.toString(), e);
		}
	}

	/**
	 * 异常类，用于包装处理过程中的重定位异常。
	 */
	static class RelocatingException extends RuntimeException {
		private static final long serialVersionUID = 1L;

		RelocatingException(String message, Throwable cause) {
			super(message, cause);
		}

		RelocatingException(String message) {
			super(message);
		}
	}
}
